package org.unidata.mdm.dq.core.service.impl.instance;

import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.unidata.mdm.dq.core.context.CleanseFunctionContext;
import org.unidata.mdm.dq.core.dto.CleanseFunctionResult;
import org.unidata.mdm.dq.core.service.impl.CleanseFunctionCacheComponent;
import org.unidata.mdm.dq.core.service.impl.function.system.AbstractSystemCleanseFunction;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunction;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunctionConfiguration;
import org.unidata.mdm.dq.core.type.measurement.DqMeasurementCategory;
import org.unidata.mdm.dq.core.type.model.instance.JavaFunctionElement;
import org.unidata.mdm.dq.core.type.model.instance.PortElement;
import org.unidata.mdm.dq.core.type.model.source.JavaCleanseFunctionSource;
import org.unidata.mdm.system.type.runtime.MeasurementPoint;
import org.unidata.mdm.system.util.TextUtils;

/**
 * @author Mikhail Mikhailov on Jan 28, 2021
 * Java function implementation.
 */
public class JavaCleanseFunctionImpl
    extends AbstractLibraryFunctionImpl<JavaCleanseFunctionSource>
    implements JavaFunctionElement {
    /**
     * Cleanse function implementation.
     */
    private CleanseFunction cleanseFunction;
    /**
     * Constructor.
     * @param acfs
     */
    public JavaCleanseFunctionImpl(JavaCleanseFunctionSource jcfs) {
        super(jcfs);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isJavaFunction() {
        return true;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public JavaFunctionElement getJavaFunction() {
        return this;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getFunctionClassName() {
        return source.getJavaClass();
    }
    // Overridden stuff.
    /**
     * {@inheritDoc}
     */
    @Override
    public String getName() {
        return isSystem()
                ? ((AbstractSystemCleanseFunction) cleanseFunction).getName()
                : super.getName();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getDisplayName() {
        return isSystem()
                ? ((AbstractSystemCleanseFunction) cleanseFunction).getDisplayName()
                : super.getDisplayName();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getDescription() {
        return isSystem()
                ? ((AbstractSystemCleanseFunction) cleanseFunction).getDescription()
                : super.getDescription();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public CleanseFunctionResult execute(CleanseFunctionContext ctx) {
        MeasurementPoint.start(DqMeasurementCategory.JAVA_CLEANSE_FUNCTION.name(), ctx.getFunctionName());

        try {
            return Objects.isNull(cleanseFunction) ? null : cleanseFunction.execute(ctx);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /*
     * Sets the function implementation and calculates state.
     */
    public void implement(String storageId, CleanseFunctionCacheComponent cfcc) {

        Pair<CleanseFunction, Exception> result = cfcc.find(storageId, this);

        CleanseFunction function = result.getKey();
        Exception exception = result.getValue();

        // Collect flags
        boolean system = false;
        boolean configurable = true;
        boolean editable = true;
        boolean ready = Objects.nonNull(function);

        if (ready) {

            CleanseFunctionConfiguration configuration = function.configure();

            system = function instanceof AbstractSystemCleanseFunction;
            editable = !system;
            configurable = Objects.isNull(configuration);
            this.cleanseFunction = function;

            // Add port definitions
            if (!configurable) {

                this.scopes.clear();
                this.scopes.addAll(configuration.getSupported());

                input.clear();
                input.putAll(configuration.getInput().stream()
                        .map(PortImpl::new)
                        .collect(Collectors.toMap(PortElement::getName, Function.identity())));

                output.clear();
                output.putAll(configuration.getOutput().stream()
                        .map(PortImpl::new)
                        .collect(Collectors.toMap(PortElement::getName, Function.identity())));

                // Update source
                source.getExecutionScopes().clear();
                source.getInputPorts().clear();
                source.getOutputPorts().clear();
                source
                    .withExecutionScopes(configuration.getSupported())
                    .withInputPorts(portsToPorts(configuration.getInput()))
                    .withOutputPorts(portsToPorts(configuration.getOutput()));

                if (!editable) {

                    AbstractSystemCleanseFunction ascf
                        = (AbstractSystemCleanseFunction) cleanseFunction;

                    source
                        .withDisplayName(ascf::getDisplayName)
                        .withDescription(ascf::getDescription);
                }
            }
        }

        if (Objects.nonNull(exception)) {
            source.setNote(TextUtils.getText(exception));
        }

        source
            .withReady(ready)
            .withSystem(system)
            .withConfigurable(configurable)
            .withEditable(editable);
    }
}
